<?php
/***
 * Candy框架 workerman Server类
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2022-3-5 17:20:22 $   
 */
 
declare (strict_types = 1);
namespace Candy\Extend\Console\Command;

use Candy\Extend\Console\{Command,Input,Output};
use Candy\Extend\Console\Input\{Option,Argument};

class Server extends Command
{
	public function configure(): void
    {
		$this->setName('WorkermanServer')
			 ->addOption('config', 'c', Option::VALUE_OPTIONAL, 'The server config path for workerman.', null)
			 ->addArgument('command', Argument::REQUIRED, "The command for workerman.")
			 ->addOption('daemon', 'd', Option::VALUE_NONE, "Daemon mode for workerman.")
			 ->addOption('gracefully', 'g', Option::VALUE_NONE, "Daemon mode for workerman.")
			 ->setDescription('Workerman Built-in Server for Candy.');
	}
	
	public function execute(Input $input, Output $output)
    {
		$config = $input->getOption('config');
		$command = $input->getArgument('command');
		if(empty($config)){
			$config = INCPATH . 'Server.inc.php';
			if(is_file($config)){
				$servers = loadConfig('Server');
				if(empty($servers)){
					$output->writeln('<error>The configuration file no content.</error>');
				}else{
					$available_commands = array('start','stop','restart','reload','status','connections',);
					if(in_array($command, $available_commands)){
						if(PHP_SAPI == 'cli'){
							$this->runServer($servers);
						}else{
							$output->writeln('<error>The server must run on php cli.</error>');
						}
					}else{
						$output->writeln("<info>Usage: php candy worker [options] [--] <command> [<mode>]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n</info>");
					}
				}
			}else{
				if($command == 'init'){
					$this->makeConfig();
					$output->writeln('<info>The configuration file was generated successfully. Please modify it to ensure the normal operation of the service.</info>');
				}else{
					$output->writeln('<error>Please run the command of name is init to configure for WorkermanServer.</error>');
				}
			}
		}else{
			if(is_file($config)){
				
			}else{
				$output->writeln('<error>The config file is not exists!</error>');
			}
		}
	}
	
	/**
     * 运行服务器
     */
    protected function runServer(array $servers): void
    {
		//引入workerman 自动加载类
		require_once(CANDYPATH . 'Vendor/Workerman/Workerman/Autoloader.php');
		
		//如果是win服务器多线程需要多脚本
		if(str_contains(strtolower(PHP_OS), 'win') === 0 && (!empty($servers['gateway']) || count($servers) > 1))
		{
			if(!empty($servers['gateway'])){
				exit('gateway use start not support windows, please use gateway_start_for_win.bat');
			}else{
				exit('windows not can use more worker in one file, please use more file to start worker');
			}
		}
			
		//注册框架基本类库路径
		\Workerman\Autoloader::setRootPath(rtrim(CANDYROOT, '/'));
		
		//创建log目录
		\Candy\Extend\Dir::create(LOGPATH);
		
		//初始化标志
		defined('ISLOADING') OR define('ISLOADING', true);
		
		//自定义记录文件地址
		\Workerman\Worker::$logFile = LOGPATH.'/workerman.log';
		\Workerman\Worker::$pidFile = LOGPATH.'/workerman.pid';
		\Workerman\Worker::$stdoutFile = LOGPATH.'/workerman.log';
		
		//使用WebServer服务
		if(!empty($servers['webserver'])){
			//开启pathinfo
			define('PATHINFO', true);
			
			//强制跳转列表
			$urlCompelList = [];
			
			//端口号
			$ports = [];
			
			//配置服务
			foreach($servers['webserver']['server'] as $sever){
				$ip = $sever['ip'] ?? '0.0.0.0';
				if(empty($sever['list']))
					exit('No domain list can\'t running.');
				
				//域名
				foreach($sever['list'] as $config){
					$context = [];
					if(isset($config['ssl'])){
						$context['ssl'] = $config['ssl'];
						$port = $config['port'] ?? 443;
						if($port == '80'){
							exit('ssl type can\'t use port 80.');
						}
					}else{
						$port = $config['port'] ?? 80;
					}
					$workername = 'web'.$port;
					if(!in_array($port, $ports)){
						$$workername = new \WebServer\Server('http://'.$ip.':'.$port, $context);
						if(isset($context['ssl'])){
							$$workername->transport = 'ssl';
						}
						
						//进程个数
						$$workername->count = $sever['count'] ?? 4;
						
						$$workername->addRoot($config['domain'], $config['root']);
						
						//开启gzip压缩 1-9
						if(isset($sever['gzip'])){
							$$workername->gzip = $sever['gzip'];
						}
						
						//开启缓存时间
						if(isset($sever['max-age'])){
							$$workername->max_age = $sever['max-age'];
						}
						
						$ports[] = $port;
						
						//开启强制跳转服务端口 默认1进程
						if(isset($config['compel']) && $config['compel'] === true){
							$urlCompelList[] = [$config['domain'], $config['root']];
						}
					}else{
						$$workername->addRoot($config['domain'], $config['root']);
					}
				}
			}
			
			if(!empty($urlCompelList)){
				if(!in_array(80, $ports)){
					$web80 = new \WebServer\Server('http://0.0.0.0:80');
					$web80->count = 1;
					$ports[] = 80;
				}
				
				//开启强制跳转服务端口 默认1进程
				if(!empty($urlCompelList)){
					foreach($urlCompelList as $compel){
						[$domain, $root] = $compel;
						$web80->addRoot($domain, $root);
						$web80::$urlCompelList[] = $domain;
					}
				}
			}
			
			//win主机只允许运行一个端口
			if(isWinOS() && count($ports) > 1){
				exit('WinOS just use one port for running.');
			}
			
			$candypath = dirname(CANDYPATH);
			$dirs = array(
				$candypath . '/Addons',
				$candypath . '/Application',
				$candypath . '/Source'
			);
		}
		
		//Rpc服务
		if(!empty($servers['rpc'])){
			new \GlobalData\RpcServer($servers['rpc']['ip'], $servers['rpc']['port'], $servers['rpc']['count']);
		}
		
		//GlobalData 进程变量共享
		if(isset($servers['globaldata']) && $servers['globaldata']['open'] === true){
			if(isset($servers['globaldata']['port']))
				new \GlobalData\Server();
			
			if(isset($servers['globaldata']['address']))
				$globaldata = new \GlobalData\Client($servers['globaldata']['address']);
				//print_r($globaldata);
				var_export(isset($globaldata->abc));
		}
		
		//如果不是win 多进程
		if(!isWinOS()){
			//在debug模式下自动reload
			$monitor_worker = new \Workerman\Worker();
			//worker的名字，方便status时标识
			$monitor_worker->name = 'FileMonitor';
			//该进程收到reload信号时，不执行reload
			$monitor_worker->reloadable = true;
			//最后更新时间
			$last_mtime = time();
			//所有被监控的文件，key为inotify id
			$monitor_files = [];
			
			// watch files only in daemon mode
			if(\Workerman\Worker::$daemonize && (defined('DEBUG') && DEBUG != 1)){
				//守护进程 和 关闭调试模式下
				$dirs = array(
					dirname(CANDYPATH) . '/Source'
				);
			}
			
			$monitor_worker->onWorkerStart = function() use ($servers, $dirs, $monitor_files, $last_mtime, $monitor_worker)
			{
				//无组件默认用轮询
				if(!extension_loaded('inotify')){
					//添加检查任务
					\Workerman\Timer::add(3, function() use ($last_mtime, $dirs)
					{
						$num = count($dirs);
						$reload = false;
						for($i=0;$i<$num;$i++){
							if(!is_dir($dirs[$i]))	continue;
							$dir_iterator = new \RecursiveDirectoryIterator($dirs[$i]);
							$iterator = new \RecursiveIteratorIterator($dir_iterator);
							foreach ($iterator as $file)
							{
								// only check php files
								if(pathinfo($file->getPathname(), PATHINFO_EXTENSION) != 'php' || basename($file->getPathname()) == 'aa512e6773d6745affa5b2ed2592936a.php')
								{
									continue;
								}
								// check mtime
								if($last_mtime < $file->getMTime())
								{
									//修复时间不更新bug
									$last_mtime = F('last_mtime');
									if($last_mtime < $file->getMTime()){
										echo $file." update and reload.\n";
										// 给父进程也就是主进程发送reload信号
										posix_kill(posix_getppid(), SIGUSR1);
										$last_mtime = $file->getMTime();
										F('last_mtime', $last_mtime);
										return;
									}
								}
							}
						}
					});
				}else{
					$num = count($dirs);
					// 初始化inotify句柄
					$monitor_worker->inotifyFd = inotify_init();
					// 设置为非阻塞
					stream_set_blocking($monitor_worker->inotifyFd, 0);
					for($i=0;$i<$num;$i++){
						if(!is_dir($dirs[$i]))	continue;
						$dir_iterator = new \RecursiveDirectoryIterator($dirs[$i]);
						$iterator = new \RecursiveIteratorIterator($dir_iterator);
						foreach ($iterator as $file)
						{
							// 只监控php文件
							if(pathinfo($file, PATHINFO_EXTENSION) != 'php' || basename($file) == 'aa512e6773d6745affa5b2ed2592936a.php')
							{
								continue;
							}
							// 把文件加入inotify监控，这里只监控了IN_MODIFY文件更新事件
							$wd = inotify_add_watch($monitor_worker->inotifyFd, $file, IN_MODIFY);
							$monitor_files[$wd] = $file;
						}
					}
					
					// 监控inotify句柄可读事件
					\Workerman\Worker::$globalEvent->add($monitor_worker->inotifyFd, \Workerman\Events\EventInterface::EV_READ, function($inotify_fd){
						global $monitor_files;
						// 读取有哪些文件事件
						$events = inotify_read($inotify_fd);
						if($events)
						{
							// 检查哪些文件被更新了
							foreach($events as $ev)
							{
								// 更新的文件
								$file = $monitor_files[$ev['wd']];
								echo $file ." update and reload\n";
								unset($monitor_files[$ev['wd']]);
								// 需要把文件重新加入监控
								$wd = inotify_add_watch($inotify_fd, $file, IN_MODIFY);
								$monitor_files[$wd] = $file;
							}
							// 给父进程也就是主进程发送reload信号
							posix_kill(posix_getppid(), SIGUSR1);
							return ;
						}
					});
				}
				
				//定时自动reload 解决低配进程阻塞问题
				if(isset($servers['reload']) && intval($servers['reload']) > 0){
					\Workerman\Timer::add($servers['reload'], function(){
						echo "all workers is reload by timertaks.\n";
						// 给父进程也就是主进程发送reload信号
						posix_kill(posix_getppid(), SIGUSR1);
					});
				}
				
				//载入自动定时任务
				if(isset($servers['task']) && $servers['task'] === true){
					//添加检查任务
					\Workerman\Timer::add(600, function(){

					});
				}
			};
		}else{
			$monitorwork = 'web' . $ports[0];
			new \FileMonitor\Monitor($$monitorwork, $dirs, 3);
		}
		
		//标识服务状态
		define('CLIWORKING', true);
		
		\Workerman\Worker::runAll();
	}
	
	/**
     * 初始化配置文件
     */
    protected function makeConfig(): void
    {
		$config = INCPATH . 'Server.inc.php';
		if (!is_dir(dirname($config))) {
            mkdir(dirname($config), 0755, true);
        }
		file_put_contents($config, base64_decode('PD9waHAKLyoqCiAqIFdvcmttYW4g5pyN5Yqh5Zmo6YWN572uCiAqKi8KcmV0dXJuIFsKCQknd2Vic2VydmVyJz0+WwoJCQknc2VydmVyJz0+WwoJCQkJCVsKCQkJCQkJJ2NvdW50Jz0+MSwJI+i/m+eoi+aVsOmHjwoJCQkJCQknZ3ppcCc9PjIsCSNnemlw5Y6L57yp562J57qnCgkJCQkJCSdtYXgtYWdlJz0+MzYwMCwJI+e8k+WtmOaXtumXtCDnp5IKCQkJCQkJI+Wfn+WQjee7keWumuWIl+ihqAoJCQkJCQknbGlzdCc9PlsKCQkJCQkJCVsKCQkJCQkJCQknZG9tYWluJz0+J2xvY2FsaG9zdCcsICPln5/lkI3mlK/mjIHms5vln5/lkI0KCQkJCQkJCQkncm9vdCc9PkNBTkRZUk9PVCwJI+e9keermeagueebruW9lQoJCQkJCQkJCSdwb3J0Jz0+ODAwOCwJCQkj56uv5Y+jCgkJCQkJCQkJI3NzbOivgeS5pgoJCQkJCQkJCS8vJ3NzbCc9PlsKCQkJCQkJCQkvLwknbG9jYWxfY2VydCcgPT4gJ2Z1bGxjaGFpbi5wZW0nLAoJCQkJCQkJCS8vCSdsb2NhbF9waycgPT4gJ3ByaXZrZXkucGVtJywKCQkJCQkJCQkvLwkndmVyaWZ5X3BlZXInID0+IGZhbHNlLAoJCQkJCQkJCS8vCS8vJ2FsbG93X3NlbGZfc2lnbmVkICc9PnRydWUsCgkJCQkJCQkJLy9dLAoJCQkJCQkJCS8vaHR0cOW8uuWItui3s+i9rGh0dHBzCgkJCQkJCQkJLy8nY29tcGVsJz0+dHJ1ZSwKCQkJCQkJCV0KCQkJCQkJXSwKCQkJCQldLAoJCQkJXSwKCQldLAoJCSdyZWxvYWQnID0+IDE4MDAsCSNXb3JrZXJtYW4g6Ieq5YqocmVsb2Fk5pe26Ze0IOenkgoJCSNycGMg5pyN5Yqh6YWN572uCgkJLy8ncnBjJz0+YXJyYXkoCgkJLy8JJ2lwJz0+JzAuMC4wLjAnLAoJCS8vCSdwb3J0Jz0+MjIwNywKCQkvLwknY291bnQnPT40LAoJCS8vKSwKCQkjR2xvYmFsRGF0YSDmnI3liqHphY3nva4KCQkvLydnbG9iYWxkYXRhJz0+YXJyYXkoCgkJLy8JJ29wZW4nPT50cnVlLAoJCS8vCSdwb3J0Jz0+MjIwNywKCQkvLwknYWRkcmVzcyc9PicxMjcuMC4wLjE6MjIwNycsCgkJLy8pLAoJCSNUYXNrIOacjeWKoeW8gOWQrwoJCS8vJ3Rhc2snPT50cnVlCgldOwo='));
    }
}